<?php

namespace StudyBuddy;

class FollowManager extends StudyBuddyObject {

    public function __construct(Registry $oRegistry) {
        $this->oRegistry = $oRegistry;
    }

    /**
     *
     * Adds question ID to array of
     * a_f_q or User object
     *
     * The reason we do this using User object
     * and not in-place update in Mongo is because
     * this operation is always (at least for now) performed
     * by the Viewer and we need the changed to be made to
     * current Viewer object so that viewer will see that
     * he is not following the question anymore.
     *
     * @todo also update a_flwrs and i_flwrs count in Question?
     * Maybe not
     * maybe ONLY i_flwrs
     *
     * @todo do the addToSet via shutdown_function so it will
     * not delay the return
     *
     * @todo experiment later with getting followers data
     * from the USERS collection during Question display
     *
     * This will add one more find() query using indexed field
     * a_f_q in USERS but the upside is - no need to also store
     * the same data in QUESTIONS and most importantly
     * the data will be live - not stale avatars but live
     * updated values of avatars and user names from USERS
     * IMPORTANT: must still $inc number of followers in QUESTION
     * ONLY if addToSet succeeds in USERS (put inside the else{})
     *
     *
     * @param User $oUser
     *
     *
     * @param mixed $Question int | Question object
     *
     * @param $addUserToQuestion bool if false then will Not append
     * array of user data to QUESTION collection's a_flwrs array
     * This option is here because in QuestionParser we already
     * adding this array when we creating the brand new question - we
     * add Viewer as the first follower automatically.
     *
     * @throws DevException if Question is not int and not Question object
     */
    public function followQuestion(User $oUser, $Question) {
        d('cp');
        $coll = $this->oRegistry->Mongo->QUESTIONS;
        $coll->ensureIndex(array('a_flwrs' => 1), array('safe' => true));

        if (!is_int($Question) && (!is_object($Question) || !($Question instanceof Question))) {
            throw new DevException('$Question can only be instance of Question class or an integer representing question id');
        }

        $qid = (is_int($Question)) ? $Question : $Question->getResourceId();
        if (!is_object($Question)) {
            $this->checkQuestionExists($qid);
        }

        $uid = $oUser->getUid();
        d('qid: ' . $qid . ' $uid: ' . $uid);
        $this->oRegistry->Dispatcher->post($oUser, 'onBeforeQuestionFollow', array('qid' => $qid));


        if (is_object($Question)) {
            $Question->addFollower($uid);
        } else {
            /**
             * If we don't have Question object
             * then add userID directly to nested array
             * of a_flwrs in QUESTIONS COLLECTION
             * using $addToSet Mongo operator, it
             * ensures that if will NOT add duplicate value
             */
            $coll->ensureIndex(array('a_flwrs', 1));
            $coll->update(array('_id' => $qid), array('$addToSet' => array('a_flwrs' => $uid)));
        }

        $this->oRegistry->Dispatcher->post($oUser, 'onQuestionFollow', array('qid' => $qid));

        return $this;
    }

    /**
     *
     * Remove question id from the a_f_q array
     * in User object, meaning User will no longer be
     * following the question
     *
     * @todo also update a_flwrs and i_flwrs count in Question? Maybe not
     * maybe ONLY i_flwrs
     *
     * @param User $oUser
     *
     * @param mixed $Question int | Question object
     *
     * @throws DevException if Question is not int and not Question object
     */
    public function unfollowQuestion(User $oUser, $Question) {
        $coll = $this->oRegistry->Mongo->QUESTIONS;
        $coll->ensureIndex(array('a_flwrs' => 1));

        if (!is_int($Question) && (!is_object($Question) || !($Question instanceof Question))) {
            throw new DevException('$Question can only be instance of Question class or an integer representing question id');
        }

        $qid = (is_int($Question)) ? $Question : $Question->getResourceId();
        d('qid: ' . $qid);
        if (!is_object($Question)) {
            $this->checkQuestionExists($qid);
        }

        $uid = $oUser->getUid();

        if (is_object($Question)) {
            $oQuestion->removeFollower($uid);
        } else {
            /**
             * If we don't have Question object
             * then add userID directly to nested array
             * of a_flwrs in QUESTIONS COLLECTION
             * using $addToSet Mongo operator, it
             * ensures that if will NOT add duplicate value
             */
            $coll->update(array('_id' => $qid), array('$pull' => array('a_flwrs' => $uid)));
        }

        $this->oRegistry->Dispatcher->post($oUser, 'onQuestionUnfollow', array('qid' => $qid));

        return $this;
    }

    /**
     * Check that record exists in QUESTIONS
     * for a given question id
     *
     * @param int $id
     *
     * @return object $this
     *
     * @throws \StudyBuddy\Exception if question with this id
     * does not exist.
     */
    protected function checkQuestionExists($qid) {

        $a = $this->oRegistry->Mongo->QUESTIONS->findOne(array('_id' => (int) $qid), array('_id'));
        if (empty($a)) {
            throw new Exception('Question with id ' . $qid . ' not found');
        }

        return $this;
    }

    /**
     * Adds the tag name to the array of a_f_t
     * of User object and increases the i_f_t by one
     * if USER does not already follow this tag
     * also increases the i_flwrs in QUESTION_TAGS collection
     * by one for this tag
     *
     *
     * @param User $oUser
     * @param string $tag
     * @throws \InvalidArgumentException if $tag is not a string
     */
    public function followTag(User $oUser, $tag) {

        if (!is_string($tag)) {
            throw new \InvalidArgumentException('$tag must be a string');
        }

        $tag = Utf8String::factory($tag)->toLowerCase()->stripTags()->trim()->valueOf();

        $aFollowed = $oUser['a_f_t'];
        d('$aFollowed: ' . print_r($aFollowed, 1));
        if (in_array($tag, $aFollowed)) {
            e('User ' . $oUser->getUid() . ' is already following question tag ' . $tag);

            return $this;
        }

        $this->oRegistry->Dispatcher->post($oUser, 'onBeforeTagFollow', array('tag' => $tag));

        $aFollowed[] = $tag;
        $oUser['a_f_t'] = $aFollowed;
        $oUser['i_f_t'] = count($aFollowed);
        $oUser->save();
        $this->oRegistry->Dispatcher->post($oUser, 'onTagFollow', array('tag' => $tag));

        $this->oRegistry->Mongo->QUESTION_TAGS->update(array('tag' => $tag), array('$inc' => array('i_flwrs' => 1)));

        return $this;
    }

    /**
     * Removes the tag name from the array of a_f_t
     * of User object and increases the i_f_t by one
     * if USER already follow this tag
     * also decreases the i_flwrs in QUESTION_TAGS collection
     * by one for this tag
     *
     *
     * @param User $oUser
     * @param string $tag
     * @throws \InvalidArgumentException if $tag is not a string
     */
    public function unfollowTag(User $oUser, $tag) {

        if (!is_string($tag)) {
            throw new \InvalidArgumentException('$tag must be a string');
        }

        $tag = Utf8String::factory($tag)->toLowerCase()->stripTags()->trim()->valueOf();


        $aFollowed = $oUser['a_f_t'];
        d('$aFollowed: ' . print_r($aFollowed, 1));

        if (false !== $key = array_search($tag, $aFollowed)) {
            d('cp unsetting key: ' . $key);
            array_splice($aFollowed, $key, 1);
            $oUser['a_f_t'] = $aFollowed;
            $oUser->save();
            $this->oRegistry->Mongo->QUESTION_TAGS->update(array('tag' => $tag), array('$inc' => array('i_flwrs' => -1)));
            $this->oRegistry->Dispatcher->post($oUser, 'onTagUnfollow', array('tag' => $tag));
        } else {
            d('tag ' . $tag . ' is not among the followed tags of this userID: ' . $oUser->getUid());
        }

        return $this;
    }

    /**
     * Process follow user request
     *
     * @param Object $oUser object of type User user who follows
     * @param int $userid id user being followed (followee)
     *
     * @return object $this
     */
    public function followUser(User $oUser, $userid) {

        if (!is_int($userid)) {
            throw new \InvalidArgumentException('$userid must be an integer');
        }

        $aFollowed = $oUser['a_f_u'];
        d('$aFollowed: ' . print_r($aFollowed, 1));

        if (in_array($userid, $aFollowed)) {
            e('User ' . $oUser->getUid() . ' is already following $userid ' . $userid);

            return $this;
        }

        $this->oRegistry->Dispatcher->post($oUser, 'onBeforeUserFollow', array('uid' => $userid));

        $aFollowed[] = $userid;
        $oUser['a_f_u'] = $aFollowed;
        $oUser['i_f_u'] = count($aFollowed);
        $oUser->save();
        $this->oRegistry->Dispatcher->post($oUser, 'onUserFollow', array('uid' => $userid));

        $this->oRegistry->Mongo->USERS->update(array('_id' => $userid), array('$inc' => array('i_flwrs' => 1)));

        return $this;
    }

    /**
     * Process unfollow user request
     *
     * @param User $oUser who is following
     * @param int $userid id user user being unfollowed
     * @throws \InvalidArgumentException
     *
     * @return object $this
     */
    public function unfollowUser(User $oUser, $userid) {

        if (!is_int($userid)) {
            throw new \InvalidArgumentException('$userid must be an integer');
        }

        $aFollowed = $oUser['a_f_u'];
        d('$aFollowed: ' . print_r($aFollowed, 1));

        if (false !== $key = array_search($userid, $aFollowed)) {
            d('cp unsetting key: ' . $key);
            array_splice($aFollowed, $key, 1);
            $oUser['a_f_u'] = $aFollowed;
            $oUser->save();
            $this->oRegistry->Mongo->USERS->update(array('_id' => $userid), array('$inc' => array('i_flwrs' => -1)));
            $this->oRegistry->Dispatcher->post($oUser, 'onUserUnfollow', array('uid' => $userid));
        } else {
            d('tag ' . $tag . ' is not among the followed tags of this userID: ' . $oUser->getUid());
        }

        return $this;
    }

    /**
     * Ensure indexes on nested arrays and
     * on email optins/optouts
     * Yes, USERS collection may seem like it has
     * too many indexes but it is still be efficient
     * than working without indexes and then filtering
     * out the optouts during the email notifications
     *
     * @return object $this
     */
    protected function ensureIndexes() {
        $coll = $this->oRegistry->Mongo->USERS;
        $coll->ensureIndex(array('a_f_t' => 1));
        $coll->ensureIndex(array('a_f_u' => 1));

        return $this;
    }

}

